/** @file
  Defines HBufferImage - the view of the file that is visible at any point,
  as well as the event handlers for editing the file

  Copyright (c) 2005 - 2018, Intel Corporation. All rights reserved. <BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "HexEditor.h"

extern EFI_HANDLE                 HImageHandleBackup;

extern HEFI_EDITOR_FILE_IMAGE     HFileImage;
extern HEFI_EDITOR_DISK_IMAGE     HDiskImage;
extern HEFI_EDITOR_MEM_IMAGE      HMemImage;

extern HEFI_EDITOR_FILE_IMAGE     HFileImageBackupVar;
extern HEFI_EDITOR_DISK_IMAGE     HDiskImageBackupVar;
extern HEFI_EDITOR_MEM_IMAGE      HMemImageBackupVar;

extern BOOLEAN                    HEditorMouseAction;

extern HEFI_EDITOR_GLOBAL_EDITOR  HMainEditor;
extern HEFI_EDITOR_GLOBAL_EDITOR  HMainEditorBackupVar;

HEFI_EDITOR_BUFFER_IMAGE          HBufferImage;
HEFI_EDITOR_BUFFER_IMAGE          HBufferImageBackupVar;

//
// for basic initialization of HBufferImage
//
HEFI_EDITOR_BUFFER_IMAGE          HBufferImageConst = {
  NULL,
  NULL,
  0,
  NULL,
  {
    0,
    0
  },
  {
    0,
    0
  },
  {
    0,
    0
  },
  0,
  TRUE,
  FALSE,
  FileTypeNone,
  NULL,
  NULL,
  NULL
};

//
// the whole edit area needs to be refreshed
//
BOOLEAN                           HBufferImageNeedRefresh;

//
// only the current line in edit area needs to be refresh
//
BOOLEAN                           HBufferImageOnlyLineNeedRefresh;

BOOLEAN                           HBufferImageMouseNeedRefresh;

/**
  Initialization function for HBufferImage

  @retval EFI_SUCCESS       The operation was successful.
  @retval EFI_LOAD_ERROR    A load error occured.
**/
EFI_STATUS
HBufferImageInit (
  VOID
  )
{
  EFI_STATUS  Status;

  //
  // basically initialize the HBufferImage
  //
  CopyMem (&HBufferImage, &HBufferImageConst, sizeof (HBufferImage));

  //
  // INIT listhead
  //
  HBufferImage.ListHead = AllocateZeroPool (sizeof (LIST_ENTRY));
  if (HBufferImage.ListHead == NULL) {
    return EFI_LOAD_ERROR;
  }

  InitializeListHead (HBufferImage.ListHead);

  HBufferImage.DisplayPosition.Row    = 2;
  HBufferImage.DisplayPosition.Column = 10;
  HBufferImage.MousePosition.Row      = 2;
  HBufferImage.MousePosition.Column   = 10;

  HBufferImage.FileImage              = &HFileImage;
  HBufferImage.DiskImage              = &HDiskImage;
  HBufferImage.MemImage               = &HMemImage;

  HBufferImageNeedRefresh             = FALSE;
  HBufferImageOnlyLineNeedRefresh     = FALSE;
  HBufferImageMouseNeedRefresh        = FALSE;

  HBufferImageBackupVar.FileImage     = &HFileImageBackupVar;
  HBufferImageBackupVar.DiskImage     = &HDiskImageBackupVar;
  HBufferImageBackupVar.MemImage      = &HMemImageBackupVar;

  Status = HFileImageInit ();
  if (EFI_ERROR (Status)) {
    return EFI_LOAD_ERROR;
  }

  Status = HDiskImageInit ();
  if (EFI_ERROR (Status)) {
    return EFI_LOAD_ERROR;
  }

  Status = HMemImageInit ();
  if (EFI_ERROR (Status)) {
    return EFI_LOAD_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Backup function for HBufferImage. Only a few fields need to be backup.
  This is for making the file buffer refresh as few as possible.

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageBackup (
  VOID
  )
{
  HBufferImageBackupVar.MousePosition   = HBufferImage.MousePosition;

  HBufferImageBackupVar.BufferPosition  = HBufferImage.BufferPosition;

  HBufferImageBackupVar.Modified        = HBufferImage.Modified;

  HBufferImageBackupVar.BufferType      = HBufferImage.BufferType;
  HBufferImageBackupVar.LowVisibleRow   = HBufferImage.LowVisibleRow;
  HBufferImageBackupVar.HighBits        = HBufferImage.HighBits;

  //
  // three kinds of buffer supported
  //   file buffer
  //   disk buffer
  //   memory buffer
  //
  switch (HBufferImage.BufferType) {
  case FileTypeFileBuffer:
    HFileImageBackup ();
    break;

  case FileTypeDiskBuffer:
    HDiskImageBackup ();
    break;

  case FileTypeMemBuffer:
    HMemImageBackup ();
    break;

  default:
    break;
  }

  return EFI_SUCCESS;
}

/**
  Free all the lines in HBufferImage.
    Fields affected:
    Lines
    CurrentLine
    NumLines
    ListHead

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageFreeLines (
  VOID
  )
{
  HFreeLines (HBufferImage.ListHead, HBufferImage.Lines);

  HBufferImage.Lines        = NULL;
  HBufferImage.CurrentLine  = NULL;
  HBufferImage.NumLines     = 0;

  return EFI_SUCCESS;
}

/**
  Cleanup function for HBufferImage

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageCleanup (
  VOID
  )
{
  EFI_STATUS  Status;

  //
  // free all the lines
  //
  Status = HBufferImageFreeLines ();

  SHELL_FREE_NON_NULL (HBufferImage.ListHead);
  HBufferImage.ListHead = NULL;

  HFileImageCleanup ();
  HDiskImageCleanup ();

  return Status;

}

/**
  Print Line on Row

  @param[in] Line     The lline to print.
  @param[in] Row      The row on screen ( begin from 1 ).
  @param[in] FRow     The FRow.
  @param[in] Orig     The original color.
  @param[in] New      The color to print with.

  @retval EFI_SUCCESS The operation was successful.
**/
EFI_STATUS
HBufferImagePrintLine (
  IN HEFI_EDITOR_LINE           *Line,
  IN UINTN                      Row,
  IN UINTN                      FRow,
  IN HEFI_EDITOR_COLOR_UNION    Orig,
  IN HEFI_EDITOR_COLOR_UNION    New

  )
{

  UINTN   Index;
  UINTN   Pos;
  BOOLEAN Selected;
  BOOLEAN BeNewColor;
  UINTN   RowStart;
  UINTN   RowEnd;
  UINTN   ColStart;
  UINTN   ColEnd;

  //
  // variable initialization
  //
  ColStart  = 0;
  ColEnd    = 0;
  Selected  = FALSE;

  //
  // print the selected area in opposite color
  //
  if (HMainEditor.SelectStart != 0 && HMainEditor.SelectEnd != 0) {
    RowStart  = (HMainEditor.SelectStart - 1) / 0x10 + 1;
    RowEnd    = (HMainEditor.SelectEnd - 1) / 0x10 + 1;

    ColStart  = (HMainEditor.SelectStart - 1) % 0x10 + 1;
    ColEnd    = (HMainEditor.SelectEnd - 1) % 0x10 + 1;

    if (FRow >= RowStart && FRow <= RowEnd) {
      Selected = TRUE;
    }

    if (FRow > RowStart) {
      ColStart = 1;
    }

    if (FRow < RowEnd) {
      ColEnd = 0x10;
    }

  }

  if (!HEditorMouseAction) {
    ShellPrintEx (
      0,
      (INT32)Row - 1,
      L"%8X ",
      ((INT32)Row - 2 + HBufferImage.LowVisibleRow - 1) * 0x10
      );

  }

  for (Index = 0; Index < 0x08 && Index < Line->Size; Index++) {

    BeNewColor = FALSE;

    if (Selected) {
      if (Index + 1 >= ColStart && Index + 1 <= ColEnd) {
        BeNewColor = TRUE;
      }
    }

    if (BeNewColor) {
      gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
    } else {
      gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
    }

    Pos = 10 + (Index * 3);
    if (Line->Buffer[Index] < 0x10) {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"0");
      Pos++;
    }

    if (Index < 0x07) {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%x ", Line->Buffer[Index]);
    } else {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%x  ", Line->Buffer[Index]);
    }

  }

  gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
  while (Index < 0x08) {
    Pos = 10 + (Index * 3);
    ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"    ");
    Index++;
  }

  while (Index < 0x10 && Index < Line->Size) {

    BeNewColor = FALSE;

    if (Selected) {
      if (Index + 1 >= ColStart && Index + 1 <= ColEnd) {
        BeNewColor = TRUE;
      }
    }

    if (BeNewColor) {
      gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
    } else {
      gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
    }

    Pos = 10 + (Index * 3) + 1;
    if (Line->Buffer[Index] < 0x10) {
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"0");
      Pos++;
    }

    ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%x ", Line->Buffer[Index]);
    Index++;
  }

  gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);
  while (Index < 0x10) {
    Pos = 10 + (Index * 3) + 1;
    ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"   ");
    Index++;
  }
  //
  // restore the original color
  //
  gST->ConOut->SetAttribute (gST->ConOut, Orig.Data & 0x7F);

  //
  // PRINT the buffer content
  //
  if (!HEditorMouseAction) {
    for (Index = 0; Index < 0x10 && Index < Line->Size; Index++) {
      Pos = ASCII_POSITION + Index;

      //
      // learned from shelle.h -- IsValidChar
      //
      if (Line->Buffer[Index] >= L' ') {
        ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%c", (CHAR16) Line->Buffer[Index]);
      } else {
        ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"%c", '.');
      }
    }

    while (Index < 0x10) {
      Pos = ASCII_POSITION + Index;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L" ");
      Index++;
    }
  }
  //
  // restore the abundant blank in hex edit area to original color
  //
  if (Selected) {
    if (ColEnd <= 7) {
      Pos = 10 + (ColEnd - 1) * 3 + 2;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L" ");
    } else if (ColEnd == 8) {
      Pos = 10 + (ColEnd - 1) * 3 + 2;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L"  ");
    } else {
      Pos = 10 + (ColEnd - 1) * 3 + 3;
      ShellPrintEx ((INT32)Pos - 1, (INT32)Row - 1, L" ");
    }
  }

  return EFI_SUCCESS;
}

/**
  Function to decide if a column number is stored in the high bits.

  @param[in] Column     The column to examine.
  @param[out] FCol      The actual column number.

  @retval TRUE      The actual column was in high bits and is now in FCol.
  @retval FALSE     There was not a column number in the high bits.
**/
BOOLEAN
HBufferImageIsAtHighBits (
  IN  UINTN Column,
  OUT UINTN *FCol
  )
{
  Column -= 10;

  //
  // NOW AFTER THE SUB, Column start from 0
  // 23 AND 24 ARE BOTH BLANK
  //
  if (Column == 24) {
    *FCol = 0;
    return FALSE;
  }

  if (Column > 24) {
    Column--;
  }

  *FCol = (Column / 3) + 1;

  if (Column % 3 == 0) {
    return TRUE;
  }

  if ((Column % 3 == 2)) {
    *FCol = 0;
  }

  return FALSE;
}

/**
  Decide if a point is in the already selected area.

  @param[in] MouseRow     The row of the point to test.
  @param[in] MouseCol     The col of the point to test.

  @retval TRUE      The point is in the selected area.
  @retval FALSE     The point is not in the selected area.
**/
BOOLEAN
HBufferImageIsInSelectedArea (
  IN UINTN MouseRow,
  IN UINTN MouseCol
  )
{
  UINTN FRow;
  UINTN RowStart;
  UINTN RowEnd;
  UINTN ColStart;
  UINTN ColEnd;
  UINTN MouseColStart;
  UINTN MouseColEnd;

  //
  // judge mouse position whether is in selected area
  //
  //
  // not select
  //
  if (HMainEditor.SelectStart == 0 || HMainEditor.SelectEnd == 0) {
    return FALSE;
  }
  //
  // calculate the select area
  //
  RowStart  = (HMainEditor.SelectStart - 1) / 0x10 + 1;
  RowEnd    = (HMainEditor.SelectEnd - 1) / 0x10 + 1;

  ColStart  = (HMainEditor.SelectStart - 1) % 0x10 + 1;
  ColEnd    = (HMainEditor.SelectEnd - 1) % 0x10 + 1;

  FRow      = HBufferImage.LowVisibleRow + MouseRow - 2;
  if (FRow < RowStart || FRow > RowEnd) {
    return FALSE;
  }

  if (FRow > RowStart) {
    ColStart = 1;
  }

  if (FRow < RowEnd) {
    ColEnd = 0x10;
  }

  MouseColStart = 10 + (ColStart - 1) * 3;
  if (ColStart > 8) {
    MouseColStart++;
  }

  MouseColEnd = 10 + (ColEnd - 1) * 3 + 1;
  if (ColEnd > 8) {
    MouseColEnd++;
  }

  if (MouseCol < MouseColStart || MouseCol > MouseColEnd) {
    return FALSE;
  }

  return TRUE;
}

/**
  Set mouse position according to HBufferImage.MousePosition.

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageRestoreMousePosition (
  VOID
  )
{
  HEFI_EDITOR_COLOR_UNION Orig;
  HEFI_EDITOR_COLOR_UNION New;
  UINTN                   FRow;
  UINTN                   FColumn;
  BOOLEAN                 HasCharacter;
  HEFI_EDITOR_LINE        *CurrentLine;
  HEFI_EDITOR_LINE        *Line;
  UINT8                   Value;
  BOOLEAN                 HighBits;

  Line = NULL;
  if (HMainEditor.MouseSupported) {

    if (HBufferImageMouseNeedRefresh) {

      HBufferImageMouseNeedRefresh = FALSE;

      //
      // if mouse position not moved and only mouse action
      // so do not need to refresh mouse position
      //
      if ((
            HBufferImage.MousePosition.Row == HBufferImageBackupVar.MousePosition.Row &&
          HBufferImage.MousePosition.Column == HBufferImageBackupVar.MousePosition.Column
        ) &&
          HEditorMouseAction
          ) {
        return EFI_SUCCESS;
      }
      //
      // backup the old screen attributes
      //
      Orig                  = HMainEditor.ColorAttributes;
      New.Data              = 0;
      New.Colors.Foreground = Orig.Colors.Background & 0xF;
      New.Colors.Background = Orig.Colors.Foreground & 0x7;

      //
      // if in selected area,
      // so do not need to refresh mouse
      //
      if (!HBufferImageIsInSelectedArea (
            HBufferImageBackupVar.MousePosition.Row,
            HBufferImageBackupVar.MousePosition.Column
            )) {
        gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
      } else {
        gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
      }
      //
      // clear the old mouse position
      //
      FRow = HBufferImage.LowVisibleRow + HBufferImageBackupVar.MousePosition.Row - 2;

      HighBits = HBufferImageIsAtHighBits (
                  HBufferImageBackupVar.MousePosition.Column,
                  &FColumn
                  );

      HasCharacter = TRUE;
      if (FRow > HBufferImage.NumLines || FColumn == 0) {
        HasCharacter = FALSE;
      } else {
        CurrentLine = HBufferImage.CurrentLine;
        Line        = HMoveLine (FRow - HBufferImage.BufferPosition.Row);

        if (Line == NULL || FColumn > Line->Size) {
          HasCharacter = FALSE;
        }

        HBufferImage.CurrentLine = CurrentLine;
      }

      ShellPrintEx (
        (INT32)HBufferImageBackupVar.MousePosition.Column - 1,
        (INT32)HBufferImageBackupVar.MousePosition.Row - 1,
        L" "
        );

      if (HasCharacter) {
        if (HighBits) {
          Value = (UINT8) (Line->Buffer[FColumn - 1] & 0xf0);
          Value = (UINT8) (Value >> 4);
        } else {
          Value = (UINT8) (Line->Buffer[FColumn - 1] & 0xf);
        }

        ShellPrintEx (
          (INT32)HBufferImageBackupVar.MousePosition.Column - 1,
          (INT32)HBufferImageBackupVar.MousePosition.Row - 1,
          L"%x",
          Value
          );
      }

      if (!HBufferImageIsInSelectedArea (
            HBufferImage.MousePosition.Row,
            HBufferImage.MousePosition.Column
            )) {
        gST->ConOut->SetAttribute (gST->ConOut, New.Data & 0x7F);
      } else {
        gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
      }
      //
      // clear the old mouse position
      //
      FRow = HBufferImage.LowVisibleRow + HBufferImage.MousePosition.Row - 2;

      HighBits = HBufferImageIsAtHighBits (
                  HBufferImage.MousePosition.Column,
                  &FColumn
                  );

      HasCharacter = TRUE;
      if (FRow > HBufferImage.NumLines || FColumn == 0) {
        HasCharacter = FALSE;
      } else {
        CurrentLine = HBufferImage.CurrentLine;
        Line        = HMoveLine (FRow - HBufferImage.BufferPosition.Row);

        if (Line == NULL || FColumn > Line->Size) {
          HasCharacter = FALSE;
        }

        HBufferImage.CurrentLine = CurrentLine;
      }

      ShellPrintEx (
        (INT32)HBufferImage.MousePosition.Column - 1,
        (INT32)HBufferImage.MousePosition.Row - 1,
        L" "
        );

      if (HasCharacter) {
        if (HighBits) {
          Value = (UINT8) (Line->Buffer[FColumn - 1] & 0xf0);
          Value = (UINT8) (Value >> 4);
        } else {
          Value = (UINT8) (Line->Buffer[FColumn - 1] & 0xf);
        }

        ShellPrintEx (
          (INT32)HBufferImage.MousePosition.Column - 1,
          (INT32)HBufferImage.MousePosition.Row - 1,
          L"%x",
          Value
          );
      }
      //
      // end of HasCharacter
      //
      gST->ConOut->SetAttribute (gST->ConOut, Orig.Data);
    }
    //
    // end of MouseNeedRefresh
    //
  }
  //
  // end of MouseSupported
  //
  return EFI_SUCCESS;
}

/**
  Set cursor position according to HBufferImage.DisplayPosition.

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageRestorePosition (
  VOID
  )
{
  //
  // set cursor position
  //
  gST->ConOut->SetCursorPosition (
        gST->ConOut,
        HBufferImage.DisplayPosition.Column - 1,
        HBufferImage.DisplayPosition.Row - 1
        );

  return EFI_SUCCESS;
}

/**
  Refresh function for HBufferImage.

  @retval EFI_SUCCESS     The operation was successful.
  @retval EFI_LOAD_ERROR  A Load error occured.

**/
EFI_STATUS
HBufferImageRefresh (
  VOID
  )
{
  LIST_ENTRY          *Link;
  HEFI_EDITOR_LINE        *Line;
  UINTN                   Row;
  HEFI_EDITOR_COLOR_UNION Orig;
  HEFI_EDITOR_COLOR_UNION New;

  UINTN                   StartRow;
  UINTN                   EndRow;
  UINTN                   FStartRow;
  UINTN                   Tmp;

  Orig                  = HMainEditor.ColorAttributes;
  New.Data              = 0;
  New.Colors.Foreground = Orig.Colors.Background;
  New.Colors.Background = Orig.Colors.Foreground;

  //
  // if it's the first time after editor launch, so should refresh
  //
  if (HEditorFirst == FALSE) {
    //
    // no definite required refresh
    // and file position displayed on screen has not been changed
    //
    if (!HBufferImageNeedRefresh &&
        !HBufferImageOnlyLineNeedRefresh &&
        HBufferImageBackupVar.LowVisibleRow == HBufferImage.LowVisibleRow
        ) {
      HBufferImageRestoreMousePosition ();
      HBufferImageRestorePosition ();
      return EFI_SUCCESS;
    }
  }

  gST->ConOut->EnableCursor (gST->ConOut, FALSE);

  //
  // only need to refresh current line
  //
  if (HBufferImageOnlyLineNeedRefresh && HBufferImageBackupVar.LowVisibleRow == HBufferImage.LowVisibleRow) {

    HBufferImagePrintLine (
      HBufferImage.CurrentLine,
      HBufferImage.DisplayPosition.Row,
      HBufferImage.BufferPosition.Row,
      Orig,
      New
      );
  } else {
    //
    // the whole edit area need refresh
    //
    if (HEditorMouseAction && HMainEditor.SelectStart != 0 && HMainEditor.SelectEnd != 0) {
      if (HMainEditor.SelectStart != HMainEditorBackupVar.SelectStart) {
        if (HMainEditor.SelectStart >= HMainEditorBackupVar.SelectStart && HMainEditorBackupVar.SelectStart != 0) {
          StartRow = (HMainEditorBackupVar.SelectStart - 1) / 0x10 + 1;
        } else {
          StartRow = (HMainEditor.SelectStart - 1) / 0x10 + 1;
        }
      } else {
        StartRow = (HMainEditor.SelectStart - 1) / 0x10 + 1;
      }

      if (HMainEditor.SelectEnd <= HMainEditorBackupVar.SelectEnd) {
        EndRow = (HMainEditorBackupVar.SelectEnd - 1) / 0x10 + 1;
      } else {
        EndRow = (HMainEditor.SelectEnd - 1) / 0x10 + 1;
      }
      //
      // swap
      //
      if (StartRow > EndRow) {
        Tmp       = StartRow;
        StartRow  = EndRow;
        EndRow    = Tmp;
      }

      FStartRow = StartRow;

      StartRow  = 2 + StartRow - HBufferImage.LowVisibleRow;
      EndRow    = 2 + EndRow - HBufferImage.LowVisibleRow;

    } else {
      //
      // not mouse selection actions
      //
      FStartRow = HBufferImage.LowVisibleRow;
      StartRow  = 2;
      EndRow    = (HMainEditor.ScreenSize.Row - 1);
    }
    //
    // no line
    //
    if (HBufferImage.Lines == NULL) {
      HBufferImageRestoreMousePosition ();
      HBufferImageRestorePosition ();
      gST->ConOut->EnableCursor (gST->ConOut, TRUE);
      return EFI_SUCCESS;
    }
    //
    // get the first line that will be displayed
    //
    Line = HMoveLine (FStartRow - HBufferImage.BufferPosition.Row);
    if (Line == NULL) {
      gST->ConOut->EnableCursor (gST->ConOut, TRUE);
      return EFI_LOAD_ERROR;
    }

    Link  = &(Line->Link);
    Row   = StartRow;
    do {
      Line = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

      //
      // print line at row
      //
      HBufferImagePrintLine (
        Line,
        Row,
        HBufferImage.LowVisibleRow + Row - 2,
        Orig,
        New
        );

      Link = Link->ForwardLink;
      Row++;
    } while (Link != HBufferImage.ListHead && Row <= EndRow);

    while (Row <= EndRow) {
      EditorClearLine (Row, HMainEditor.ScreenSize.Column, HMainEditor.ScreenSize.Row);
      Row++;
    }
    //
    // while not file end and not screen full
    //
  }

  HBufferImageRestoreMousePosition ();
  HBufferImageRestorePosition ();

  HBufferImageNeedRefresh         = FALSE;
  HBufferImageOnlyLineNeedRefresh = FALSE;
  gST->ConOut->EnableCursor (gST->ConOut, TRUE);

  return EFI_SUCCESS;
}

/**
  Read an image into a buffer friom a source.

  @param[in] FileName     Pointer to the file name.  OPTIONAL and ignored if not FileTypeFileBuffer.
  @param[in] DiskName     Pointer to the disk name.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskOffset   Offset into the disk.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskSize     Size of the disk buffer.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] MemOffset    Offset into the Memory.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] MemSize      Size of the Memory buffer.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] BufferType   The type of buffer to save.  IGNORED.
  @param[in] Recover      TRUE for recovermode, FALSE otherwise.

  @return EFI_SUCCESS     The operation was successful.
**/
EFI_STATUS
HBufferImageRead (
  IN CONST CHAR16                   *FileName,
  IN CONST CHAR16                   *DiskName,
  IN UINTN                          DiskOffset,
  IN UINTN                          DiskSize,
  IN UINTN                          MemOffset,
  IN UINTN                          MemSize,
  IN EDIT_FILE_TYPE                 BufferType,
  IN BOOLEAN                        Recover
  )
{
  EFI_STATUS                      Status;
  EDIT_FILE_TYPE                  BufferTypeBackup;

  //
  // variable initialization
  //
  Status = EFI_SUCCESS;
  HBufferImage.BufferType = BufferType;

  //
  // three types of buffer supported
  //   file buffer
  //   disk buffer
  //   memory buffer
  //
  BufferTypeBackup = HBufferImage.BufferType;

  switch (BufferType) {
  case FileTypeFileBuffer:
    Status = HFileImageRead (FileName, Recover);
    break;

  case FileTypeDiskBuffer:
    Status = HDiskImageRead (DiskName, DiskOffset, DiskSize, Recover);
    break;

  case FileTypeMemBuffer:
    Status = HMemImageRead (MemOffset, MemSize, Recover);
    break;

  default:
    Status = EFI_NOT_FOUND;
    break;
  }

  if (EFI_ERROR (Status)) {
    HBufferImage.BufferType = BufferTypeBackup;
  }

  return Status;
}

/**
  Save the current image.

  @param[in] FileName     Pointer to the file name.  OPTIONAL and ignored if not FileTypeFileBuffer.
  @param[in] DiskName     Pointer to the disk name.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskOffset   Offset into the disk.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] DiskSize     Size of the disk buffer.  OPTIONAL and ignored if not FileTypeDiskBuffer.
  @param[in] MemOffset    Offset into the Memory.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] MemSize      Size of the Memory buffer.  OPTIONAL and ignored if not FileTypeMemBuffer.
  @param[in] BufferType   The type of buffer to save.  IGNORED.

  @return EFI_SUCCESS     The operation was successful.
**/
EFI_STATUS
HBufferImageSave (
  IN CHAR16                         *FileName,
  IN CHAR16                         *DiskName,
  IN UINTN                          DiskOffset,
  IN UINTN                          DiskSize,
  IN UINTN                          MemOffset,
  IN UINTN                          MemSize,
  IN EDIT_FILE_TYPE                 BufferType
  )
{
  EFI_STATUS                      Status;
  EDIT_FILE_TYPE                  BufferTypeBackup;

  //
  // variable initialization
  //
  Status            = EFI_SUCCESS;
  BufferTypeBackup  = HBufferImage.BufferType;

  switch (HBufferImage.BufferType) {
  //
  // file buffer
  //
  case FileTypeFileBuffer:
    Status = HFileImageSave (FileName);
    break;

  //
  // disk buffer
  //
  case FileTypeDiskBuffer:
    Status = HDiskImageSave (DiskName, DiskOffset, DiskSize);
    break;

  //
  // memory buffer
  //
  case FileTypeMemBuffer:
    Status = HMemImageSave (MemOffset, MemSize);
    break;

  default:
    Status = EFI_NOT_FOUND;
    break;
  }

  if (EFI_ERROR (Status)) {
    HBufferImage.BufferType = BufferTypeBackup;
  }

  return Status;
}

/**
  Create a new line and append it to the line list.
    Fields affected:
    NumLines
    Lines

  @retval NULL    create line failed.
  @return         the line created.

**/
HEFI_EDITOR_LINE *
HBufferImageCreateLine (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;

  //
  // allocate for line structure
  //
  Line = AllocateZeroPool (sizeof (HEFI_EDITOR_LINE));
  if (Line == NULL) {
    return NULL;
  }

  Line->Signature = EFI_EDITOR_LINE_LIST;
  Line->Size      = 0;

  HBufferImage.NumLines++;

  //
  // insert to line list
  //
  InsertTailList (HBufferImage.ListHead, &Line->Link);

  if (HBufferImage.Lines == NULL) {
    HBufferImage.Lines = CR (
                          HBufferImage.ListHead->ForwardLink,
                          HEFI_EDITOR_LINE,
                          Link,
                          EFI_EDITOR_LINE_LIST
                          );
  }

  return Line;
}

/**
  Free the current image.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageFree (
  VOID
  )
{
  //
  // free all lines
  //
  HBufferImageFreeLines ();

  return EFI_SUCCESS;
}

/**
  change char to int value based on Hex.

  @param[in] Char     The input char.

  @return The character's index value.
  @retval -1  The operation failed.
**/
INTN
HBufferImageCharToHex (
  IN CHAR16 Char
  )
{
  //
  // change the character to hex
  //
  if (Char >= L'0' && Char <= L'9') {
    return (Char - L'0');
  }

  if (Char >= L'a' && Char <= L'f') {
    return (Char - L'a' + 10);
  }

  if (Char >= L'A' && Char <= L'F') {
    return (Char - L'A' + 10);
  }

  return -1;
}

/**
  Add character.

  @param[in] Char -- input char.

  @retval EFI_SUCCESS             The operation was successful.
  @retval EFI_OUT_OF_RESOURCES    A memory allocation failed.
**/
EFI_STATUS
HBufferImageAddChar (
  IN  CHAR16  Char
  )
{
  HEFI_EDITOR_LINE  *Line;
  HEFI_EDITOR_LINE  *NewLine;
  INTN              Value;
  UINT8             Old;
  UINTN             FRow;
  UINTN             FCol;
  BOOLEAN           High;

  Value = HBufferImageCharToHex (Char);

  //
  // invalid input
  //
  if (Value == -1) {
    return EFI_SUCCESS;
  }

  Line  = HBufferImage.CurrentLine;
  FRow  = HBufferImage.BufferPosition.Row;
  FCol  = HBufferImage.BufferPosition.Column;
  High  = HBufferImage.HighBits;

  //
  // only needs to refresh current line
  //
  HBufferImageOnlyLineNeedRefresh = TRUE;

  //
  // not a full line and beyond the last character
  //
  if (FCol > Line->Size) {
    //
    // cursor always at high 4 bits
    // and always put input to the low 4 bits
    //
    Line->Buffer[Line->Size] = (UINT8) Value;
    Line->Size++;
    High = FALSE;
  } else {

    Old = Line->Buffer[FCol - 1];

    //
    // always put the input to the low 4 bits
    //
    Old                     = (UINT8) (Old & 0x0f);
    Old                     = (UINT8) (Old << 4);
    Old                     = (UINT8) (Value + Old);
    Line->Buffer[FCol - 1]  = Old;

    //
    // at the low 4 bits of the last character of a full line
    // so if no next line, need to create a new line
    //
    if (!High && FCol == 0x10) {

      HBufferImageOnlyLineNeedRefresh = FALSE;
      HBufferImageNeedRefresh         = TRUE;

      if (Line->Link.ForwardLink == HBufferImage.ListHead) {
        //
        // last line
        //
        // create a new line
        //
        NewLine = HBufferImageCreateLine ();
        if (NewLine == NULL) {
          return EFI_OUT_OF_RESOURCES;
        }
        //
        // end of NULL
        //
      }
      //
      // end of == ListHead
      //
    }
    //
    // end of == 0x10
    //
    // if already at end of this line, scroll it to the start of next line
    //
    if (FCol == 0x10 && !High) {
      //
      // definitely has next line
      //
      FRow++;
      FCol  = 1;
      High  = TRUE;
    } else {
      //
      // if not at end of this line, just move to next column
      //
      if (!High) {
        FCol++;
      }

      if (High) {
        High = FALSE;
      } else {
        High = TRUE;
      }

    }
    //
    // end of ==FALSE
    //
  }
  //
  // move cursor to right
  //
  HBufferImageMovePosition (FRow, FCol, High);

  if (!HBufferImage.Modified) {
    HBufferImage.Modified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Delete the previous character.

  @retval EFI_SUCCESS   The operationw as successful.
**/
EFI_STATUS
HBufferImageDoBackspace (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;

  UINTN             FileColumn;
  UINTN             FPos;
  BOOLEAN           LastLine;

  //
  // variable initialization
  //
  LastLine = FALSE;

  //
  // already the first character
  //
  if (HBufferImage.BufferPosition.Row == 1 && HBufferImage.BufferPosition.Column == 1) {
    return EFI_SUCCESS;
  }

  FPos        = (HBufferImage.BufferPosition.Row - 1) * 0x10 + HBufferImage.BufferPosition.Column - 1;

  FileColumn  = HBufferImage.BufferPosition.Column;

  Line        = HBufferImage.CurrentLine;
  LastLine    = FALSE;
  if (Line->Link.ForwardLink == HBufferImage.ListHead && FileColumn > 1) {
    LastLine = TRUE;
  }

  HBufferImageDeleteCharacterFromBuffer (FPos - 1, 1, NULL);

  //
  // if is the last line
  // then only this line need to be refreshed
  //
  if (LastLine) {
    HBufferImageNeedRefresh         = FALSE;
    HBufferImageOnlyLineNeedRefresh = TRUE;
  } else {
    HBufferImageNeedRefresh         = TRUE;
    HBufferImageOnlyLineNeedRefresh = FALSE;
  }

  if (!HBufferImage.Modified) {
    HBufferImage.Modified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  ASCII key + Backspace + return.

  @param[in] Char               The input char.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_LOAD_ERROR        A load error occured.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
HBufferImageDoCharInput (
  IN  CHAR16  Char
  )
{
  EFI_STATUS  Status;

  Status = EFI_SUCCESS;

  switch (Char) {
  case 0:
    break;

  case 0x08:
    Status = HBufferImageDoBackspace ();
    break;

  case 0x09:
  case 0x0a:
  case 0x0d:
    //
    // Tabs, Returns are thought as nothing
    //
    break;

  default:
    //
    // DEAL WITH ASCII CHAR, filter out thing like ctrl+f
    //
    if (Char > 127 || Char < 32) {
      Status = StatusBarSetStatusString (L"Unknown Command");
    } else {
      Status = HBufferImageAddChar (Char);
    }

    break;
  }

  return Status;
}

/**
  Check user specified FileRow is above current screen.

  @param[in] FileRow  Row of file position ( start from 1 ).

  @retval TRUE   It is above the current screen.
  @retval FALSE  It is not above the current screen.

**/
BOOLEAN
HAboveCurrentScreen (
  IN  UINTN FileRow
  )
{
  if (FileRow < HBufferImage.LowVisibleRow) {
    return TRUE;
  }

  return FALSE;
}

/**
  Check user specified FileRow is under current screen.

  @param[in] FileRow    Row of file position ( start from 1 ).

  @retval TRUE      It is under the current screen.
  @retval FALSE     It is not under the current screen.

**/
BOOLEAN
HUnderCurrentScreen (
  IN  UINTN FileRow
  )
{
  if (FileRow > HBufferImage.LowVisibleRow + (HMainEditor.ScreenSize.Row - 2) - 1) {
    return TRUE;
  }

  return FALSE;
}

/**
  According to cursor's file position, adjust screen display.

  @param[in] NewFilePosRow    Row of file position ( start from 1 ).
  @param[in] NewFilePosCol    Column of file position ( start from 1 ).
  @param[in] HighBits         Cursor will on high4 bits or low4 bits.
**/
VOID
HBufferImageMovePosition (
  IN UINTN    NewFilePosRow,
  IN UINTN    NewFilePosCol,
  IN BOOLEAN  HighBits
  )
{
  INTN    RowGap;
  UINTN   Abs;
  BOOLEAN Above;
  BOOLEAN Under;
  UINTN   NewDisplayCol;

  //
  // CALCULATE gap between current file position and new file position
  //
  RowGap                = NewFilePosRow - HBufferImage.BufferPosition.Row;

  Under                 = HUnderCurrentScreen (NewFilePosRow);
  Above                 = HAboveCurrentScreen (NewFilePosRow);

  HBufferImage.HighBits = HighBits;

  //
  // if is below current screen
  //
  if (Under) {
    //
    // display row will be unchanged
    //
    HBufferImage.BufferPosition.Row = NewFilePosRow;
  } else {
    if (Above) {
      //
      // has enough above line, so display row unchanged
      // not has enough above lines, so the first line is
      // at the first display line
      //
      if (NewFilePosRow < (HBufferImage.DisplayPosition.Row - 2 + 1)) {
        HBufferImage.DisplayPosition.Row = NewFilePosRow + 2 - 1;
      }

      HBufferImage.BufferPosition.Row = NewFilePosRow;
    } else {
      //
      // in current screen
      //
      HBufferImage.BufferPosition.Row = NewFilePosRow;
      if (RowGap <= 0) {
        Abs = (UINTN)ABS(RowGap);
        HBufferImage.DisplayPosition.Row -= Abs;
      } else {
        HBufferImage.DisplayPosition.Row += RowGap;
      }

    }
  }

  HBufferImage.LowVisibleRow = HBufferImage.BufferPosition.Row - (HBufferImage.DisplayPosition.Row - 2);

  //
  // always in current screen
  //
  HBufferImage.BufferPosition.Column  = NewFilePosCol;

  NewDisplayCol                       = 10 + (NewFilePosCol - 1) * 3;
  if (NewFilePosCol > 0x8) {
    NewDisplayCol++;
  }

  if (!HighBits) {
    NewDisplayCol++;
  }

  HBufferImage.DisplayPosition.Column = NewDisplayCol;

  //
  // let CurrentLine point to correct line;
  //
  HBufferImage.CurrentLine = HMoveCurrentLine (RowGap);

}

/**
  Scroll cursor to right.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollRight (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;

  //
  // scroll right will always move to the high4 bits of the next character
  //
  HBufferImageNeedRefresh         = FALSE;
  HBufferImageOnlyLineNeedRefresh = FALSE;

  Line = HBufferImage.CurrentLine;

  FRow = HBufferImage.BufferPosition.Row;
  FCol = HBufferImage.BufferPosition.Column;

  //
  // this line is not full and no next line
  //
  if (FCol > Line->Size) {
    return EFI_SUCCESS;
  }
  //
  // if already at end of this line, scroll it to the start of next line
  //
  if (FCol == 0x10) {
    //
    // has next line
    //
    if (Line->Link.ForwardLink != HBufferImage.ListHead) {
      FRow++;
      FCol = 1;

    } else {
      return EFI_SUCCESS;
    }
  } else {
    //
    // if not at end of this line, just move to next column
    //
    FCol++;

  }

  HBufferImageMovePosition (FRow, FCol, TRUE);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to left.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollLeft (
  VOID
  )
{

  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;

  HBufferImageNeedRefresh         = FALSE;
  HBufferImageOnlyLineNeedRefresh = FALSE;

  Line = HBufferImage.CurrentLine;

  FRow = HBufferImage.BufferPosition.Row;
  FCol = HBufferImage.BufferPosition.Column;

  //
  // if already at start of this line, so move to the end of previous line
  //
  if (FCol <= 1) {
    //
    // has previous line
    //
    if (Line->Link.BackLink != HBufferImage.ListHead) {
      FRow--;
      Line  = CR (Line->Link.BackLink, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);
      FCol  = Line->Size;
    } else {
      return EFI_SUCCESS;
    }
  } else {
    //
    // if not at start of this line, just move to previous column
    //
    FCol--;
  }

  HBufferImageMovePosition (FRow, FCol, TRUE);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to the next line

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollDown (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;
  BOOLEAN           HighBits;

  Line      = HBufferImage.CurrentLine;

  FRow      = HBufferImage.BufferPosition.Row;
  FCol      = HBufferImage.BufferPosition.Column;
  HighBits  = HBufferImage.HighBits;

  //
  // has next line
  //
  if (Line->Link.ForwardLink != HBufferImage.ListHead) {
    FRow++;
    Line = CR (Line->Link.ForwardLink, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

    //
    // if the next line is not that long, so move to end of next line
    //
    if (FCol > Line->Size) {
      FCol      = Line->Size + 1;
      HighBits  = TRUE;
    }

  } else {
    return EFI_SUCCESS;
  }

  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to previous line

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageScrollUp (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;

  Line  = HBufferImage.CurrentLine;

  FRow  = HBufferImage.BufferPosition.Row;
  FCol  = HBufferImage.BufferPosition.Column;

  //
  // has previous line
  //
  if (Line->Link.BackLink != HBufferImage.ListHead) {
    FRow--;

  } else {
    return EFI_SUCCESS;
  }

  HBufferImageMovePosition (FRow, FCol, HBufferImage.HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to next page

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImagePageDown (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;
  UINTN             Gap;
  BOOLEAN           HighBits;

  Line      = HBufferImage.CurrentLine;

  FRow      = HBufferImage.BufferPosition.Row;
  FCol      = HBufferImage.BufferPosition.Column;
  HighBits  = HBufferImage.HighBits;

  //
  // has next page
  //
  if (HBufferImage.NumLines >= FRow + (HMainEditor.ScreenSize.Row - 2)) {
    Gap = (HMainEditor.ScreenSize.Row - 2);
  } else {
    //
    // MOVE CURSOR TO LAST LINE
    //
    Gap = HBufferImage.NumLines - FRow;
  }
  //
  // get correct line
  //
  Line = HMoveLine (Gap);

  //
  // if that line, is not that long, so move to the end of that line
  //
  if (Line != NULL && FCol > Line->Size) {
    FCol      = Line->Size + 1;
    HighBits  = TRUE;
  }

  FRow += Gap;

  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to previous page

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImagePageUp (
  VOID
  )
{
  UINTN             FRow;
  UINTN             FCol;
  UINTN             Gap;
  INTN              Retreat;

  FRow  = HBufferImage.BufferPosition.Row;
  FCol  = HBufferImage.BufferPosition.Column;

  //
  // has previous page
  //
  if (FRow > (HMainEditor.ScreenSize.Row - 2)) {
    Gap = (HMainEditor.ScreenSize.Row - 2);
  } else {
    //
    // the first line of file will displayed on the first line of screen
    //
    Gap = FRow - 1;
  }

  Retreat = Gap;
  Retreat = -Retreat;

  FRow -= Gap;

  HBufferImageMovePosition (FRow, FCol, HBufferImage.HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to start of line

  @retval EFI_SUCCESS  The operation was successful.
**/
EFI_STATUS
HBufferImageHome (
  VOID
  )
{
  UINTN             FRow;
  UINTN             FCol;
  BOOLEAN           HighBits;

  //
  // curosr will at the high bit
  //
  FRow      = HBufferImage.BufferPosition.Row;
  FCol      = 1;
  HighBits  = TRUE;

  //
  // move cursor position
  //
  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Scroll cursor to end of line.

  @retval EFI_SUCCESS  Teh operation was successful.
**/
EFI_STATUS
HBufferImageEnd (
  VOID
  )
{
  HEFI_EDITOR_LINE  *Line;
  UINTN             FRow;
  UINTN             FCol;
  BOOLEAN           HighBits;

  //
  // need refresh mouse
  //
  HBufferImageMouseNeedRefresh  = TRUE;

  Line                          = HBufferImage.CurrentLine;

  FRow                          = HBufferImage.BufferPosition.Row;

  if (Line->Size == 0x10) {
    FCol      = Line->Size;
    HighBits  = FALSE;
  } else {
    FCol      = Line->Size + 1;
    HighBits  = TRUE;
  }
  //
  // move cursor position
  //
  HBufferImageMovePosition (FRow, FCol, HighBits);

  return EFI_SUCCESS;
}

/**
  Get the size of the open buffer.

  @retval The size in bytes.
**/
UINTN
HBufferImageGetTotalSize (
  VOID
  )
{
  UINTN             Size;

  HEFI_EDITOR_LINE  *Line;

  //
  // calculate the total size of whole line list's buffer
  //
  if (HBufferImage.Lines == NULL) {
    return 0;
  }

  Line = CR (
          HBufferImage.ListHead->BackLink,
          HEFI_EDITOR_LINE,
          Link,
          EFI_EDITOR_LINE_LIST
          );
  //
  // one line at most 0x10
  //
  Size = 0x10 * (HBufferImage.NumLines - 1) + Line->Size;

  return Size;
}

/**
  Delete character from buffer.

  @param[in] Pos      Position, Pos starting from 0.
  @param[in] Count    The Count of characters to delete.
  @param[out] DeleteBuffer    The DeleteBuffer.

  @retval EFI_SUCCESS Success
**/
EFI_STATUS
HBufferImageDeleteCharacterFromBuffer (
  IN  UINTN         Pos,
  IN  UINTN         Count,
  OUT UINT8         *DeleteBuffer
  )
{
  UINTN             Index;

  VOID              *Buffer;
  UINT8             *BufferPtr;
  UINTN             Size;

  HEFI_EDITOR_LINE  *Line;
  LIST_ENTRY    *Link;

  UINTN             OldFCol;
  UINTN             OldFRow;
  UINTN             OldPos;

  UINTN             NewPos;

  EFI_STATUS        Status;

  Size      = HBufferImageGetTotalSize ();

  if (Size < Count) {
    return EFI_LOAD_ERROR;
  }

  if (Size == 0) {
    return EFI_SUCCESS;
  }

  //
  // relocate all the HBufferImage fields
  //
  OldFRow = HBufferImage.BufferPosition.Row;
  OldFCol = HBufferImage.BufferPosition.Column;
  OldPos  = (OldFRow - 1) * 0x10 + OldFCol - 1;

  if (Pos > 0) {
    //
    // has character before it,
    // so locate according to block's previous character
    //
    NewPos = Pos - 1;

  } else {
    //
    // has no character before it,
    // so locate according to block's next character
    //
    NewPos = 0;
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  Buffer = AllocateZeroPool (Size);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  HBufferImageListToBuffer (Buffer, Size);

  BufferPtr = (UINT8 *) Buffer;

  //
  // pass deleted buffer out
  //
  if (DeleteBuffer != NULL) {
    for (Index = 0; Index < Count; Index++) {
      DeleteBuffer[Index] = BufferPtr[Pos + Index];
    }
  }
  //
  // delete the part from Pos
  //
  for (Index = Pos; Index < Size - Count; Index++) {
    BufferPtr[Index] = BufferPtr[Index + Count];
  }

  Size -= Count;

  HBufferImageFreeLines ();

  Status = HBufferImageBufferToList (Buffer, Size);
  FreePool (Buffer);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Link = HMainEditor.BufferImage->ListHead->ForwardLink;
  for (Index = 0; Index < NewPos / 0x10; Index++) {
    Link = Link->ForwardLink;
  }

  Line                      = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);
  HBufferImage.CurrentLine  = Line;

  //
  // if current cursor position if inside select area
  // then move it to the block's NEXT character
  //
  if (OldPos >= Pos && OldPos < (Pos + Count)) {
    NewPos = Pos;
  } else {
    if (OldPos < Pos) {
      NewPos = OldPos;
    } else {
      NewPos = OldPos - Count;
    }
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  return EFI_SUCCESS;
}

/**
  Add character to buffer, add before pos.

  @param[in] Pos        Position, Pos starting from 0.
  @param[in] Count      Count of characters to add.
  @param[in] AddBuffer  Add buffer.

  @retval EFI_SUCCESS   Success.
**/
EFI_STATUS
HBufferImageAddCharacterToBuffer (
  IN  UINTN          Pos,
  IN  UINTN          Count,
  IN  UINT8          *AddBuffer
  )
{
  INTN              Index;

  VOID              *Buffer;
  UINT8             *BufferPtr;
  UINTN             Size;

  HEFI_EDITOR_LINE  *Line;

  LIST_ENTRY    *Link;

  UINTN             OldFCol;
  UINTN             OldFRow;
  UINTN             OldPos;

  UINTN             NewPos;

  Size      = HBufferImageGetTotalSize ();

  //
  // relocate all the HBufferImage fields
  //
  OldFRow = HBufferImage.BufferPosition.Row;
  OldFCol = HBufferImage.BufferPosition.Column;
  OldPos  = (OldFRow - 1) * 0x10 + OldFCol - 1;

  //
  // move cursor before Pos
  //
  if (Pos > 0) {
    NewPos = Pos - 1;
  } else {
    NewPos = 0;
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  Buffer = AllocateZeroPool (Size + Count);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  HBufferImageListToBuffer (Buffer, Size);

  BufferPtr = (UINT8 *) Buffer;

  //
  // get a place to add
  //
  for (Index = (INTN) (Size + Count - 1); Index >= (INTN) Pos; Index--) {
    BufferPtr[Index] = BufferPtr[Index - Count];
  }
  //
  // add the buffer
  //
  for (Index = (INTN) 0; Index < (INTN) Count; Index++) {
    BufferPtr[Index + Pos] = AddBuffer[Index];
  }

  Size += Count;

  HBufferImageFreeLines ();

  HBufferImageBufferToList (Buffer, Size);

  FreePool (Buffer);

  Link = HMainEditor.BufferImage->ListHead->ForwardLink;
  for (Index = 0; Index < (INTN) NewPos / 0x10; Index++) {
    Link = Link->ForwardLink;
  }

  Line                      = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);
  HBufferImage.CurrentLine  = Line;

  if (OldPos >= Pos) {
    NewPos = OldPos + Count;
  } else {
    NewPos = OldPos;
  }

  HBufferImageMovePosition (NewPos / 0x10 + 1, NewPos % 0x10 + 1, TRUE);

  return EFI_SUCCESS;
}

/**
  Delete current character from line.

  @retval EFI_SUCCESS   The operationw as successful.
**/
EFI_STATUS
HBufferImageDoDelete (
  VOID
  )
{

  HEFI_EDITOR_LINE  *Line;

  BOOLEAN           LastLine;
  UINTN             FileColumn;
  UINTN             FPos;

  FPos        = (HBufferImage.BufferPosition.Row - 1) * 0x10 + HBufferImage.BufferPosition.Column - 1;

  FileColumn  = HBufferImage.BufferPosition.Column;

  Line        = HBufferImage.CurrentLine;

  //
  // if beyond the last character
  //
  if (FileColumn > Line->Size) {
    return EFI_SUCCESS;
  }

  LastLine = FALSE;
  if (Line->Link.ForwardLink == HBufferImage.ListHead) {
    LastLine = TRUE;
  }

  HBufferImageDeleteCharacterFromBuffer (FPos, 1, NULL);

  //
  // if is the last line
  // then only this line need to be refreshed
  //
  if (LastLine) {
    HBufferImageNeedRefresh         = FALSE;
    HBufferImageOnlyLineNeedRefresh = TRUE;
  } else {
    HBufferImageNeedRefresh         = TRUE;
    HBufferImageOnlyLineNeedRefresh = FALSE;
  }

  if (!HBufferImage.Modified) {
    HBufferImage.Modified = TRUE;
  }

  return EFI_SUCCESS;
}

/**
  Change the raw buffer to a list of lines for the UI.

  @param[in] Buffer   The pointer to the buffer to fill.
  @param[in] Bytes    The size of the buffer in bytes.

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_OUT_OF_RESOURCES  A memory allocation failed.
**/
EFI_STATUS
HBufferImageBufferToList (
  IN VOID   *Buffer,
  IN UINTN  Bytes
  )
{
  UINTN             TempI;
  UINTN             TempJ;
  UINTN             Left;
  HEFI_EDITOR_LINE  *Line;
  UINT8             *BufferPtr;

  TempI         = 0;
  Left      = 0;
  BufferPtr = (UINT8 *) Buffer;

  //
  // parse file content line by line
  //
  while (TempI < Bytes) {
    if (Bytes - TempI >= 0x10) {
      Left = 0x10;
    } else {
      Left = Bytes - TempI;
    }

    //
    // allocate a new line
    //
    Line = HBufferImageCreateLine ();
    if (Line == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    Line->Size = Left;

    for (TempJ = 0; TempJ < Left; TempJ++) {
      Line->Buffer[TempJ] = BufferPtr[TempI];
      TempI++;
    }

  }

  //
  // last line is a full line, SO create a new line
  //
  if (Left == 0x10 || Bytes == 0) {
    Line = HBufferImageCreateLine ();
    if (Line == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  return EFI_SUCCESS;
}

/**
  Change the list of lines from the UI to a raw buffer.

  @param[in] Buffer   The pointer to the buffer to fill.
  @param[in] Bytes    The size of the buffer in bytes.

  @retval EFI_SUCCESS   The operation was successful.
**/
EFI_STATUS
HBufferImageListToBuffer (
  IN VOID   *Buffer,
  IN UINTN  Bytes
  )
{
  UINTN             Count;
  UINTN             Index;
  HEFI_EDITOR_LINE  *Line;
  LIST_ENTRY    *Link;
  UINT8             *BufferPtr;

  //
  // change the line list to a large buffer
  //
  if (HBufferImage.Lines == NULL) {
    return EFI_SUCCESS;
  }

  Link      = &HBufferImage.Lines->Link;
  Count     = 0;
  BufferPtr = (UINT8 *) Buffer;

  //
  // deal line by line
  //
  while (Link != HBufferImage.ListHead) {

    Line = CR (Link, HEFI_EDITOR_LINE, Link, EFI_EDITOR_LINE_LIST);

    //@todo shouldn't this be an error???
    if (Count + Line->Size > Bytes) {
      return EFI_SUCCESS;
    }

    for (Index = 0; Index < Line->Size; Index++) {
      BufferPtr[Index] = Line->Buffer[Index];
    }

    Count += Line->Size;
    BufferPtr += Line->Size;

    Link = Link->ForwardLink;
  }

  return EFI_SUCCESS;
}

/**
  Move the mouse in the image buffer.

  @param[in] TextX    The x-coordinate.
  @param[in] TextY    The y-coordinate.
**/
VOID
HBufferImageAdjustMousePosition (
  IN INT32 TextX,
  IN INT32 TextY
  )
{
  UINTN TempX;
  UINTN TempY;
  UINTN AbsX;
  UINTN AbsY;

  //
  // TextX and TextY is mouse movement data returned by mouse driver
  // This function will change it to MousePosition
  //
  //
  // get absolute TempX value
  //
  if (TextX >= 0) {
    AbsX = TextX;
  } else {
    AbsX = -TextX;
  }
  //
  // get absolute TempY value
  //
  if (TextY >= 0) {
    AbsY = TextY;
  } else {
    AbsY = -TextY;
  }

  TempX = HBufferImage.MousePosition.Column;
  TempY = HBufferImage.MousePosition.Row;

  if (TextX >= 0) {
    TempX += TextX;
  } else {
    if (TempX >= AbsX) {
      TempX -= AbsX;
    } else {
      TempX = 0;
    }
  }

  if (TextY >= 0) {
    TempY += TextY;
  } else {
    if (TempY >= AbsY) {
      TempY -= AbsY;
    } else {
      TempY = 0;
    }
  }
  //
  // check whether new mouse column position is beyond screen
  // if not, adjust it
  //
  if (TempX >= 10 && TempX <= (10 + 0x10 * 3 - 1)) {
    HBufferImage.MousePosition.Column = TempX;
  } else if (TempX < 10) {
    HBufferImage.MousePosition.Column = 10;
  } else if (TempX > (10 + 0x10 * 3 - 1)) {
    HBufferImage.MousePosition.Column = 10 + 0x10 * 3 - 1;
  }
  //
  // check whether new mouse row position is beyond screen
  // if not, adjust it
  //
  if (TempY >= 2 && TempY <= (HMainEditor.ScreenSize.Row - 1)) {
    HBufferImage.MousePosition.Row = TempY;
  } else if (TempY < 2) {
    HBufferImage.MousePosition.Row = 2;
  } else if (TempY > (HMainEditor.ScreenSize.Row - 1)) {
    HBufferImage.MousePosition.Row = (HMainEditor.ScreenSize.Row - 1);
  }

}

/**
  Dispatch input to different handler

  @param[in] Key    The input key:
                     the keys can be:
                       ASCII KEY
                        Backspace/Delete
                        Direction key: up/down/left/right/pgup/pgdn
                        Home/End
                        INS

  @retval EFI_SUCCESS           The operation was successful.
  @retval EFI_LOAD_ERROR        A load error occured.
  @retval EFI_OUT_OF_RESOURCES  A Memory allocation failed.
**/
EFI_STATUS
HBufferImageHandleInput (
  IN  EFI_INPUT_KEY *Key
  )
{
  EFI_STATUS  Status;

  Status = EFI_SUCCESS;

  switch (Key->ScanCode) {
  //
  // ordinary key
  //
  case SCAN_NULL:
    Status = HBufferImageDoCharInput (Key->UnicodeChar);
    break;

  //
  // up arrow
  //
  case SCAN_UP:
    Status = HBufferImageScrollUp ();
    break;

  //
  // down arrow
  //
  case SCAN_DOWN:
    Status = HBufferImageScrollDown ();
    break;

  //
  // right arrow
  //
  case SCAN_RIGHT:
    Status = HBufferImageScrollRight ();
    break;

  //
  // left arrow
  //
  case SCAN_LEFT:
    Status = HBufferImageScrollLeft ();
    break;

  //
  // page up
  //
  case SCAN_PAGE_UP:
    Status = HBufferImagePageUp ();
    break;

  //
  // page down
  //
  case SCAN_PAGE_DOWN:
    Status = HBufferImagePageDown ();
    break;

  //
  // delete
  //
  case SCAN_DELETE:
    Status = HBufferImageDoDelete ();
    break;

  //
  // home
  //
  case SCAN_HOME:
    Status = HBufferImageHome ();
    break;

  //
  // end
  //
  case SCAN_END:
    Status = HBufferImageEnd ();
    break;

  default:
    Status = StatusBarSetStatusString (L"Unknown Command");
    break;
  }

  return Status;
}

